﻿using System.Collections.Generic;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Profiling;
using Debug = UnityEngine.Debug;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Mars.GpuInstance
{
    public class InstanceProceduralManager : MonoBehaviour
    {
        public int maxDrawDis = 500;
        public bool castShadow = true;

        [Header("资源缓存的时间")]
        public int assetCacheTime = 900;
        
        public bool isEnable = true;
        
        
        private bool cullBoundsDirty = false;
        private Camera drawCamera;
        private JobHandle jobHandle = default;

        private bool isInit = false;

        private bool anyModelChange = false;  //是否有模型变化，有变化就要刷新texture streaming
        private Vector3 preCameraPos = Vector3.zero;
        private Quaternion preCameraRot = Quaternion.identity;
        
        private float4[] frustumPlanesArray = new float4[6];
        private NativeArray<float4> frustumPlanes;

        private NativeList<RefSimpleBound> cullBounds;

        public Dictionary<string, InstanceRuntimeData> instanceRuntimeDatas = new ();
        //错误的路径
        public HashSet<string> errorKey = new();

        private int autoInstancerId = 1;
        
        
        private static InstanceProceduralManager instance;
        public static InstanceProceduralManager GetInstance()
        {
#if UNITY_EDITOR
            if (instance == null)
#else
            if (instance is null)
#endif
            {
                instance = GameObject.FindObjectOfType<InstanceProceduralManager>();
                if (instance == null)
                {
                    var go = new GameObject("InstanceProceduralManager");
                    instance = go.AddComponent<InstanceProceduralManager>();
                    DontDestroyOnLoad(go);
                    instance.Init(instance.maxDrawDis);
                }
            }
            return instance;
        }

        public void Init(int drawDis = 500)
        {
            isInit = true;
            maxDrawDis = drawDis;
            frustumPlanes = new NativeArray<float4>(6, Allocator.Persistent);
            cullBounds = new NativeList<RefSimpleBound>(50, Allocator.Persistent);
        }

        public int GetNextId()
        {
            return autoInstancerId--;
        }

        public void SetCastShadow(bool enable)
        {
            castShadow = enable;
        }

        public void UpdateDrawDis(int drawDis)
        {
            maxDrawDis = drawDis;
        }
        

        public void SetDrawable(string path,  bool drawable)
        {
            if (instanceRuntimeDatas.TryGetValue(path, out var item))
            {
                item.drawable = drawable;
            }
        }

        public bool GetDrawable(string path)
        {
            if (instanceRuntimeDatas.TryGetValue(path, out var item))
            {
                return item.drawable;
            }

            return false;
        }

        #region 判断是否要刷新
        
        private InstancerDataDirtyType IsNeedUpdateData()
        {
            if (cullBoundsDirty)
            {
                return InstancerDataDirtyType.CULLBOUND_CHANGE;
            }

            if (IsCameraChange())
            {
                return InstancerDataDirtyType.CAMERA_CHANGE;
            }
            else
            {
                var instanceRuntimeDataValues = instanceRuntimeDatas.Values;
                foreach (var instanceRuntimeData in instanceRuntimeDataValues)
                {
                    if (instanceRuntimeData.dataModified)
                    {
                        return InstancerDataDirtyType.INSTANCER_CHANGE;
                    }
                }
            }

            return InstancerDataDirtyType.NONE;
        }

        private bool IsCameraChange()
        {
            if (drawCamera == null || !drawCamera.enabled)
            {
                return false;
            }

            var cameraTransform = drawCamera.transform;
            var pos = cameraTransform.position;
            var rot = cameraTransform.rotation;
            if (preCameraPos != pos || preCameraRot != rot)
            {
                preCameraPos = pos;
                preCameraRot = rot;
                return true;
            }

            return false;
        }
        
        public bool IsNeedUpdateStreaming()
        {
            if (anyModelChange)
            {
                return true;
            }
            
            return IsCameraChange();
        }

        #endregion

        public void Dispose()
        {
            isInit = false;
            if (frustumPlanes.IsCreated)
            {
                frustumPlanes.Dispose();
            }

            if (cullBounds.IsCreated)
            {
                cullBounds.Dispose();
            }

            var instanceRuntimeDataValues = instanceRuntimeDatas.Values;
            foreach (var instanceRuntimeData in instanceRuntimeDataValues)
            {
                instanceRuntimeData.Dispose();
            }
            
            instanceRuntimeDatas.Clear();
        }

        public void Clear()
        {
            if (cullBounds.IsCreated)
            {
                cullBounds.Clear();
            }
            
            var instanceRuntimeDataValues = instanceRuntimeDatas.Values;
            foreach (var instanceRuntimeData in instanceRuntimeDataValues)
            {
                instanceRuntimeData.Dispose();
            }
            
            instanceRuntimeDatas.Clear();
            
            if (InstanceCacheDataManager.GetInstance() != null)
            {
                InstanceCacheDataManager.GetInstance().Clear();
            }
            
            if (InstanceResourceManager.GetInstance() != null)
            {
                InstanceResourceManager.GetInstance().Clear();
            }
        }

        #region 添加移除相关

        public void AddInstance(string path, int id, float4x4 matrix, bool ignoreCullBound = false, int cullingDis = 0, float4 campColor = default)
        {
            if (errorKey.Contains(path))
            {
                return;
            }

            InitRuntimeData(path);
            if (instanceRuntimeDatas.TryGetValue(path, out var item))
            {
                item.AddInstance(id, matrix, ignoreCullBound, cullingDis, campColor);
                anyModelChange = true;
            } 
        }

        public int AddInstance(string path, float3 pos, bool ignoreCullBound = false)
        {
            var id = GetNextId();
            AddInstance(path, id, float4x4.TRS(pos, quaternion.identity, 1), ignoreCullBound);
            return id;
        }
        
        public void AddInstance(string path, int id, float4x4 matrix, bool ignoreCullBound = false)
        {
            AddInstance(path, id, matrix, ignoreCullBound, 0);
        }

        public void RemoveInstance(string path, int id)
        {
            if (instanceRuntimeDatas.TryGetValue(path, out var item))
            {
                item.RemoveInstance(id);
                anyModelChange = true;
            }
        }
        

        public void ClearInstance(string path)
        {
            if (instanceRuntimeDatas.TryGetValue(path, out var item))
            {
                item.ClearInstance();
                anyModelChange = true;
            }
        }
        
        
        public void UpdateSpecCullingDis(string path, int id, int dis)
        {
            if (instanceRuntimeDatas.TryGetValue(path, out var item))
            {
                item.UpdateSpecCullingDis(id, dis);
            }
        }
        
        public void ShowInstance(string path, int id, bool show)
        {
            if (instanceRuntimeDatas.TryGetValue(path, out var item))
            {
                item.ShowInstance(id, show);
            }
        }

        #endregion
        
        #region 裁剪相关
        
        public void AddCullBound(SimpleBound bound)
        {
            cullBoundsDirty = true;
            bool hasFind = false;
            int count = cullBounds.Length;
            for (int i = 0; i < count; i++)
            {
                var refBound = cullBounds[i];
                if (refBound.bound.Equals(bound))
                {
                    cullBounds[i] = new RefSimpleBound()
                    {
                        bound = bound,
                        refCount = refBound.refCount + 1
                    };
                    hasFind = true;
                    break;
                }
            }
            
            if (!hasFind)
            {
                cullBounds.Add(new RefSimpleBound()
                {
                    bound = bound,
                    refCount = 1
                });
            }
        }

        public void RemoveCullBound(SimpleBound bound)
        {
            cullBoundsDirty = true;
            int count = cullBounds.Length;
            for (int i = 0; i < count; i++)
            {
                var refBound = cullBounds[i];
                if (refBound.bound.Equals(bound))
                {
                    if (refBound.refCount <= 1)
                    {
                        cullBounds.RemoveAt(i);
                    }
                    else
                    {
                        cullBounds[i] = new RefSimpleBound()
                        {
                            bound = bound,
                            refCount = refBound.refCount - 1
                        };
                    }
                    break;
                }
            }
        }

        public void ClearCullBound()
        {
            cullBounds.Clear();
        }

        #endregion
        
        #region 引擎方法
        
        protected void Start()
        {
            if (!isInit)
            {
                Init(maxDrawDis);
            }
        }

        public void Update()
        {
            if (drawCamera == null)
            {
                drawCamera = Camera.main;
            }
            
            if (!isEnable)
            {
                return;
            }
            
            if (Time.frameCount % 2 != 0)
            {
                return;
            }
            
#if UNITY_EDITOR
            else
            {
                if (SceneView.lastActiveSceneView && drawCamera == SceneView.lastActiveSceneView.camera)
                {
                    drawCamera = Camera.main;
                }
            }
            if (drawCamera == null)
            {
                drawCamera = SceneView.lastActiveSceneView.camera;
            }
#endif
            
            UpdateFrame();
            CheckRecycle();
            
        }

        public void LateUpdate()
        {
            if (!isEnable)
            {
                return;
            }

            Draw();
        }
        
        protected void OnDestroy()
        {
            Dispose();
            instance = null;
        }
        
        protected void OnApplicationQuit()
        {
            InstanceCacheDataManager.GetInstance().Dispose();
            InstanceResourceManager.GetInstance().Dispose();
        }

        #endregion

        #region 回收释放相关

        private List<string> needRemoveList = new();
        private void CheckRecycle()
        {
            var frameCount = Time.frameCount;
            var cacheMgr = InstanceCacheDataManager.GetInstance();
            foreach (var instanceRuntimeData in instanceRuntimeDatas)
            {
                if (frameCount - instanceRuntimeData.Value.clearTimeFrame > assetCacheTime) //没有使用的资源30秒后清除掉
                {
                    cacheMgr.ReturnInstanceRuntimeData(instanceRuntimeData.Value);
                    needRemoveList.Add(instanceRuntimeData.Key);
                }
            }

            foreach (var key in needRemoveList)
            {
                instanceRuntimeDatas.Remove(key);
            }
            needRemoveList.Clear();

            if (frameCount % 50 == 0)
            {
                InstanceCacheDataManager.GetInstance().Tick();
            }
        }

        #endregion

        #region 绘制相关

        public void UpdateFrame()
        {
            if (drawCamera == null || !drawCamera.enabled)
            {
                return;
            }

            var updateType = IsNeedUpdateData();
            if (updateType != InstancerDataDirtyType.NONE)
            {
                cullBoundsDirty = false;
                anyModelChange = true;

                if (!jobHandle.IsCompleted)
                {
                    jobHandle.Complete();
                }

                frustumPlanes.CopyFrom(MathUtil.GetFrustumPlanes(drawCamera, frustumPlanesArray));
                var instanceRuntimeDataValues = instanceRuntimeDatas.Values;

                Profiler.BeginSample("Add Job");

                var jobHandleList = new NativeList<JobHandle>(instanceRuntimeDataValues.Count, Allocator.Temp);

                foreach (var instanceRuntimeData in instanceRuntimeDataValues)
                {
                    if (!instanceRuntimeData.dataModified && updateType == InstancerDataDirtyType.INSTANCER_CHANGE)
                    {
                        continue;
                    }
                    instanceRuntimeData.VisibilityCalculation(frustumPlanes, drawCamera.transform.position, jobHandleList, cullBounds);
                }

                Profiler.EndSample();

                Profiler.BeginSample("Complete Job");
                jobHandle = JobHandle.CombineDependencies(jobHandleList.AsArray());
                jobHandle.Complete();
                Profiler.EndSample();
            }
        }

        
        public void Draw()
        {
            var instanceRuntimeDataValues = instanceRuntimeDatas.Values;
            foreach (var instanceRuntimeData in instanceRuntimeDataValues)
            {
                instanceRuntimeData.Draw(castShadow);
            }
        }

        #endregion
        
        #region 初始化批次相关

        public bool InitRuntimeData(string path)
        {
            if (instanceRuntimeDatas.ContainsKey(path))
            {
                return true;
            }

            if (errorKey.Contains(path))
            {
                return false;
            }

            var config = InstanceResourceManager.GetInstance().LoadInstanceGenConfig(path);
            if (config)
            {
                InitRuntimeDataByConfig(path, config);
            }
            else
            {
                errorKey.Add(path);
                Debug.LogError($"[InstanceProceduralManager] {path}不存在");
                return false;
            }
            
            return true;
        }

        private void InitRuntimeDataByConfig(string path, InstanceGenConfig config)
        {
            var runtimeData = InstanceCacheDataManager.GetInstance().GetInstanceRuntimeData();
            runtimeData.UpdateBox(config.boxCenter, config.boxExtents);
            var showRange = new float2(0, 0);
            var len = config.lodConfigs.Count;
            runtimeData.lodRange = new NativeArray<int>(len + 1, Allocator.Persistent);
            var lodIndex = 0;
            
    
            bool hasValidMeshLod = false;
            for (int i = 0; i < len; i++)
            {
                var lodConfig = config.lodConfigs[i];
                
                if (lodConfig.showRange > showRange.y)
                {
                    showRange.y = math.min(lodConfig.showRange, maxDrawDis);
                }



                hasValidMeshLod = true;
                for (int j = 0; j < lodConfig.renderConfigs.Count; j++)
                {
                    var lodRenderConfig = lodConfig.renderConfigs[j];
                    var batch = InstanceCacheDataManager.GetInstance().GetInstanceRenderBatch();
                    batch.UpdateLodLevel(lodIndex);
                    batch.UpdateDrawRange(showRange);
                    batch.UpdateRenderAsset(lodRenderConfig.mesh, lodRenderConfig.materias);

                    var tmpMpb = new MaterialPropertyBlock();
                    tmpMpb.SetMatrix(InstancerConstants.TRANSFORM_OFFSET, lodRenderConfig.transformOffset);
                    batch.UpdateOffsetMatrix(lodRenderConfig.transformOffset);
                    batch.UpdateMpb(tmpMpb);
                    runtimeData.AddBatch(batch);
                }

                runtimeData.lodRange[lodIndex++] = (int) showRange.y;
                showRange.x = lodConfig.showRange;
            }
            

            showRange.x = 0;

            if (hasValidMeshLod)
            {
                if (config.shadowLod == ShadowLod.AUTO_SELECT)
                {
                    var lodConfig = config.lodConfigs[^1];

                    for (int j = 0; j < lodConfig.renderConfigs.Count; j++)
                    {
                        var lodRenderConfig = lodConfig.renderConfigs[j];

                        var shadowBatch = InstanceCacheDataManager.GetInstance().GetInstanceRenderBatch();
                        shadowBatch.UpdateLodLevel(len - 1);
                        shadowBatch.UpdateDrawRange(showRange);
                        shadowBatch.UpdateRenderAsset(lodRenderConfig.mesh, lodRenderConfig.materias);

                        var tmpMpb = new MaterialPropertyBlock();
                        tmpMpb.SetMatrix(InstancerConstants.TRANSFORM_OFFSET, lodRenderConfig.transformOffset);
                        shadowBatch.UpdateOffsetMatrix(lodRenderConfig.transformOffset);
                        shadowBatch.UpdateMpb(tmpMpb);
                        runtimeData.AddShadowBatch(shadowBatch);
                    }
                }
            }



            runtimeData.UpdateShadowLod(config.shadowLod);
            runtimeData.UpdateShowRange(showRange);
            runtimeData.forceRender = config.forceRender;
            instanceRuntimeDatas.Add(path, runtimeData);
        }

        #endregion
    }
}
